package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"time"
)

// tips:
// 大端序（BigEndian）：高位字节存储在低地址，低位字节存储在高地址。【数值的高位字节优先占据低地址。】
// 例如，整数 0x12345678 在大端序中的存储顺序是 12 34 56 78。
//
// 小端序（LittleEndian）：低位字节存储在低地址，高位字节存储在高地址。【数值的低位字节优先占据低地址。】
// 例如，整数 0x12345678 在小端序中的存储顺序是 78 56 34 12。
var (
	timeout int64
	size    int
	count   int
	typ     uint8 = 8
	code    uint8 = 0
)

// main
//
//	@Description:
//	go run .\main.go -w 150 -l 64 -n 8  www.baidu.com
//	go run .\main.go -w 150 -l 32 -n 8  www.baidu.com
func main() {
	GetCommandArgs()
	desIp := os.Args[len(os.Args)-1] //获取最后一个参数
	conn, err := net.DialTimeout("ip:icmp", desIp, time.Duration(timeout)*time.Millisecond)
	if err != nil {
		fmt.Println("连接失败")
		return
	}
	defer func(conn net.Conn) {
		err := conn.Close()
		if err != nil {
			fmt.Println("关闭连接失败")
		}
	}(conn)

	fmt.Printf("正在 Ping %s [%s] 具有 %d 字节的数据: \n", desIp, conn.RemoteAddr(), size)
	for i := 0; i < count; i++ {
		t1 := time.Now()
		var imcp *ICMP = &ICMP{
			Type:     typ,  //8位Type
			Code:     code, //8位code
			Checksum: 0,    //16位校验和
			ID:       1,    //16位ID
			Sequence: 1,    //16位序列号
		}

		//正文
		data := make([]byte, size)                    //默认size=32
		var buffer bytes.Buffer                       //声明一个字节缓冲区 buffer，用于高效拼接二进制数据。
		binary.Write(&buffer, binary.BigEndian, imcp) //将 imcp 结构体按大端序编码为二进制，写入 buffer。
		buffer.Write(data)                            //将原始字节切片 data 追加到 buffer 的末尾（此时 buffer 包含 imcp 序列化后的数据 + data）。
		data = buffer.Bytes()                         //将缓冲区中的完整二进制数据转换为字节切片，覆盖原有 data。
		checkSum := CheckSum(data)
		data[2] = byte(checkSum >> 8) //校验和占用index=2 index=3 两个字节，[2]取高位字节，[3]取低位字节
		data[3] = byte(checkSum)
		conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))
		_, err = conn.Write(data)
		if err != nil {
			log.Println(err)
			continue
		}
		buf := make([]byte, 65535)
		n, err := conn.Read(buf)
		if err != nil {
			log.Println(err)
			continue
		}
		ts := time.Since(t1).Milliseconds()
		fmt.Printf("来自 %d.%d.%d.%d 的回复: 字节=%d 时间=%d ms TTL=%d\n", buf[12], buf[13], buf[14], buf[15], n-28, ts, buf[8])

	}

}

// GetCommandArgs
//
//	@Description: 从命令行获取参数
// -w timeout 等待每次回复的超时时间（毫秒）
// -l size 发送缓冲区大小，单位字节
// -n count 要发送的回显请求数

func GetCommandArgs() {
	flag.Int64Var(&timeout, "w", 1000, "等待每次回复的超时时间（毫秒）")
	flag.IntVar(&size, "l", 32, "发送缓冲区大小，单位字节")
	flag.IntVar(&count, "n", 4, "要发送的回显请求数")
	flag.Parse()
}

// ICMP
// @Description: ICMP报文：
// 8位Type|8位code|16位校验和|16位ID|16位序列号|内容~
type ICMP struct {
	Type     uint8  //8位Type
	Code     uint8  //8位code
	Checksum uint16 //16位校验和
	ID       uint16 //16位ID
	Sequence uint16 //16位序列号
}

func CheckSum(data []byte) uint16 {
	length := len(data)
	index := 0
	var sum uint32
	for length > 1 {
		//当前字节左移8位，作为高位字节。下一字节的数据作为低位字节。
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		length -= 2
		index += 2
	}
	if length != 0 {
		sum += uint32(data[index])
	}

	//右移动16位，然后与16位数据进行异或运算，直到为0。
	hi16 := sum >> 16
	for hi16 != 0 {
		sum = hi16 + uint32(uint16(sum))
		hi16 = sum >> 16
	}

	return uint16(^sum)
}
